package jflowmap.model.ontology.calculation;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import jflowmap.model.ontology.Context;
import jflowmap.model.ontology.Dimension;
import jflowmap.model.ontology.Filters;
import jflowmap.model.ontology.Match;
import jflowmap.model.ontology.Value;
import jflowmap.util.Pair;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class VariableDefinition {
	private final String name;
	private final Context context;
	private final Map<String, Constraint> constraints = Maps.newHashMap();
	private final Map<String, VariableDefinition> boundVariables = Maps.newHashMap();
	
	public VariableDefinition(String name, Context context) {
		this.name = name;
		this.context = context;
	}
	
	public String getName() {
		return name;
	}
	
	
	public void addConstraint (Constraint constraint) {
		constraint.setVariableDefinition(this);
		this.constraints.put(constraint.getName(), constraint);		
	}
	
	public void addConstraint (BoundConstraint constraint) {
		constraint.getUnboundConstraint().getVariableDefinition().boundVariables.put(getName(), this);
		this.addConstraint((Constraint)constraint);
	}	
	
	
	public boolean hasConstraint (String name) {
		return this.constraints.containsKey(name);
	}
	
	public Constraint getConstraint (String name) {
		return this.constraints.get(name);
	}
		
	public List<Variable> getUnboundVariables() {		
		List<Variable> result = Lists.newArrayList();
		
		//calculate the Cartesian product to get all the unbound variables
		List<Set<Pair<String, Dimension>>> namedDimensions = Lists.newArrayList();		
		for (Constraint constraint: this.constraints.values()) {
			namedDimensions.add(constraint.getDimensions());
		}
		Set<List<Pair<String, Dimension>>> filtering = Sets.cartesianProduct(namedDimensions);
		
		//filter out the values from the context matching the unbound variables
		//and create all the unbound variables 
		final Set<Value> values = context.getValues();
		for (List<Pair<String, Dimension>> eachVariable: filtering) {
			Match match = new Match();
			Variable variable = new Variable(this.name, this);
			for (Pair<String, Dimension> namedDimension: eachVariable) {
				match.add(namedDimension.second());
				variable.addConstraint(namedDimension.first(), namedDimension.second());
			}
			Set<Value> finalValues = Filters.matching(match).filter(values);

			if (finalValues.size() == 1) {
				variable.setValue(finalValues.iterator().next());
				
				//bind the bound variables to the unbound one
				for (VariableDefinition boundVariable: this.boundVariables.values()) {
					final Variable bound = boundVariable.getBoundVariableFor(variable, values);
					if (bound != null) {
						
						System.out.println("\n\n\n\nBinding variable " + bound + " to " +  variable + "\n\n\n\n");
						
						variable.bindVariable(bound);
					}
				}
				result.add(variable);
			}
		}
		
		return result;		
	}	
	
	private Variable getBoundVariableFor(Variable variable, Set<Value> values) {
		
		final Match match = new Match();
		final Variable result = new Variable(this.getName(), this);
		
		System.out.println("Adding the following bound variables to variable " + variable);
		System.out.println("\t" + this);
		
		for (Entry<String, Constraint> constraintEntry: this.constraints.entrySet()) {
			constraintEntry.getValue().setUnboundVariable(variable);
			final Set<Pair<String, Dimension>> otherDimension = constraintEntry.getValue().getDimensions();
			
			//TODO
			assert(otherDimension.size() == 1);
			
			final Dimension dim = otherDimension.iterator().next().second();
			result.addConstraint(constraintEntry.getKey(), dim);
			match.add(dim);
		}

		final Set<Value> foundValues = Filters.matching(match).filter(values);
		if (foundValues.size() == 1) {
			result.setValue(foundValues.iterator().next());
			return result;
		}
		
		System.out.println(foundValues.size() + " values found matching " + match);
		
		System.out.println("\n");
		return null;
	}

	@Override
	public String toString() {
		return "VariableDefinition [name=" + name + ", constraints="
				+ constraints + " ]";
	}
}